use std::any::Any;
use std::fmt::Debug;
use std::future::Future;
use std::sync::Arc;

use lazy_static::lazy_static;
pub use redis::{AsyncCommands, RedisResult, ToRedisArgs};
use redis::aio::MultiplexedConnection;

use crate::async_single_flight;
use crate::redis_cache::script::{REDIS_GET_SCRIPT, REDIS_STORE_SCRIPT};

const SEVEN_DAYS: usize = 7 * 24 * 60 * 60 * 1000;
const PLACEHOLDER: usize = 60 * 1000;
const INVALID_CACHE_CODE: &str = "-410";

lazy_static! {
    static ref REDIS_GET_SCRIPT_REF: redis::Script = redis::Script::new(REDIS_GET_SCRIPT);
    static ref REDIS_STORE_SCRIPT_REF: redis::Script = redis::Script::new(REDIS_STORE_SCRIPT);
}

pub trait Cache {
    fn get<F, Fu, T, E>(&self, key: &str, f: F) -> impl Future<Output=Result<Option<Arc<T>>, Arc<E>>>
        where
            F: FnOnce() -> Fu,
            Fu: Future<Output=Result<Option<T>, E>>,
            T: ToRedisArgs + serde::de::DeserializeOwned + Any + Send + Sync + Debug,
            E: Any + Send + Sync + From<String>;
    fn del<E>(&self, key: &str) -> impl Future<Output=Result<Arc<()>, Arc<E>>>
        where
            E: Any + Send + Sync + From<String>;
}

#[derive(Debug, derive_builder::Builder)]
#[builder(build_fn(validate = "Self::validate_expire"))]
pub struct RedisCache {
    conn: MultiplexedConnection,
    #[builder(default = "SEVEN_DAYS")]
    expire: usize,
    #[builder(setter(skip))]
    single_flight: async_single_flight::AsyncGroup,
    // TODO: random expire time
}

impl RedisCache {
    pub fn new(conn: MultiplexedConnection, expire: usize) -> Self {
        Self {
            conn,
            expire,
            single_flight: async_single_flight::AsyncGroup::default(),
        }
    }

    async fn store<T>(&self, key: &str, value: T) -> Result<bool, redis::RedisError>
        where
            T: ToRedisArgs + Debug,
    {
        let mut conn = self.conn.clone();
        let res: bool = REDIS_STORE_SCRIPT_REF
            .key(key)
            .arg(value)
            .arg(self.expire)
            .arg(INVALID_CACHE_CODE)
            .arg(PLACEHOLDER) // 无效缓存的过期时间, 1 分钟
            .invoke_async(&mut conn)
            .await?;
        Ok(res)
    }

    async fn get_<F, Fu, T, E>(&self, key: &str, f: F) -> Result<Option<T>, E>
        where
            F: FnOnce() -> Fu,
            Fu: Future<Output=Result<Option<T>, E>>,
            T: ToRedisArgs + serde::de::DeserializeOwned + Any + Send + Sync + Debug,
            E: Any + Send + Sync + From<String>,
    {
        let mut conn = self.conn.clone();
        let res: Option<String> = REDIS_GET_SCRIPT_REF
            .key(key)
            .arg(INVALID_CACHE_CODE)
            .invoke_async(&mut conn)
            .await
            .unwrap_or_else(|e| {
                println!("获取缓存失败: {e:#?}");
                None
            });

        if let Some(v) = res {
            if v.as_str() != INVALID_CACHE_CODE {
                return match serde_json::from_str(v.as_str()) {
                    Ok(v) => Ok(v),
                    Err(e) => {
                        println!("解析缓存失败: {e:#?}");
                        Err(format!("解析缓存失败: {e}").into())
                    }
                };
            }
        }
        let res = f().await?;
        if let Some(ref v) = res {
            // 不能说因为设置缓存不成功, 就不返回DB中已拿到的数据, 所以这里忽略结果
            // 如果没有设置好, 那么下一次还是查询会去DB中拿, 然后继续尝试设置缓存
            let r: RedisResult<bool> = self.store(key, v).await;
            match r {
                Ok(true) => {}
                _ => {
                    println!("设置缓存失败: {r:#?}");
                }
            }
        };
        Ok(res)
    }
}

impl RedisCacheBuilder {
    pub fn validate_expire(&self) -> Result<(), String> {
        if self.expire == Some(0) {
            return Err("expire must be greater than 0".to_owned());
        }
        Ok(())
    }
}

impl Cache for RedisCache {
    async fn get<F, Fu, T, E>(&self, key: &str, f: F) -> Result<Option<Arc<T>>, Arc<E>>
        where
            F: FnOnce() -> Fu,
            Fu: Future<Output=Result<Option<T>, E>>,
            T: ToRedisArgs + serde::de::DeserializeOwned + Any + Send + Sync + Debug,
            E: Any + Send + Sync + From<String>,
    {
        let single_flight = self.single_flight.clone();
        single_flight.work(key, || self.get_(key, f)).await
    }

    async fn del<E>(&self, key: &str) -> Result<Arc<()>, Arc<E>>
        where
            E: Any + Send + Sync + From<String>,
    {
        let single_flight = self.single_flight.clone();
        let result = single_flight
            .work(key, || async move {
                let options =
                    redis::SetOptions::default().with_expiration(redis::SetExpiry::PX(PLACEHOLDER));
                let res: Result<Option<()>, E> = self
                    .conn
                    .clone()
                    .set_options(key, INVALID_CACHE_CODE, options)
                    .await
                    .map_err(|e| format!("redis set error: {e}").into());
                res
            })
            .await;
        result.map(|v| v.unwrap())
    }
}
